interface EditorProps {}
export class Editor {
    private readonly iframe: HTMLIFrameElement
    constructor(private readonly container: HTMLDivElement, private readonly props?: EditorProps) {
        const iframe = document.createElement('iframe')
        this.iframe = iframe
        iframe.style.width = '100%'
        iframe.style.height = '100%'
        iframe.style.border = 'none'
        this.container.appendChild(iframe)

        const innerDocument = iframe.contentDocument!
        innerDocument.designMode = 'on'
        innerDocument.open()
        innerDocument.write(`
<!DOCTYPE html>
<html>
    <head></head>
    <body></body>
</html>
            `)
        innerDocument.close()
        innerDocument.body.appendChild(Utils.createNewLine())
        innerDocument.addEventListener('keydown', this.onDocumentKeydown.bind(this))
        innerDocument.addEventListener('selectionchange', this.onDocumentSelectionchange.bind(this))
        innerDocument.body.addEventListener('focus', this.onBodyFocus.bind(this))
        new MutationObserver(this.onContentChange.bind(this)).observe(innerDocument.body, { childList: true })
    }
    get innerDocument() {
        return this.iframe.contentDocument!
    }
    get selection() {
        return this.innerDocument.getSelection()!
    }
    get selectionRange() {
        if (this.selection.rangeCount === 0) {
            this.selection.selectAllChildren(this.innerDocument.body)
            this.selection.collapseToEnd()
        }
        return this.selection.getRangeAt(0)
    }
    destroy() {}
    onDocumentKeydown(e: KeyboardEvent) {
        if (e.ctrlKey) {
            e.preventDefault()
            console.log(e.key)
            if (e.key === 'z') {
            }
        } else {
            if (e.key === 'Enter') {
                requestAnimationFrame(this.correctNewLine.bind(this))
            }
        }
    }
    onDocumentSelectionchange() {
        console.log(this.selectionRange)
    }
    onContentChange() {}
    onBodyFocus(_e: FocusEvent) {}
    correctNewLine() {
        const { startContainer } = this.selectionRange
        if (startContainer.nodeName !== 'DIV') {
            return
        }
        const rootNode = this.innerDocument.body
        const startNode = Utils.getTopParent(startContainer, rootNode) as ChildNode
        const newLine = Utils.createNewLine()
        startNode.replaceWith(newLine)
        this.selectionRange.selectNode(newLine.childNodes[0])
        this.selection.collapseToStart()
    }
    formatBlock(format: 'H1' | 'H2' | 'H3' | 'H4' | 'H5' | 'H6' | 'P') {
        const { startContainer, startOffset, endContainer, endOffset } = this.selectionRange
        const topNodeList = Utils.getTopNodeListInSelection(this.innerDocument.body, this.selectionRange)
        topNodeList.forEach(a => Utils.replaceTagName(a, format))
        this.selection.setBaseAndExtent(startContainer, startOffset, endContainer, endOffset)
    }
    formatInline(format: 'STRONG' | 'U' | 'EM' | 'S' | 'CODE' | 'SUP' | 'SUB') {
        const { startContainer, startOffset, endContainer, endOffset, collapsed } = this.selectionRange
        if (collapsed || this.selectionRange.toString() === '') {
            return
        }
        let newStartContainer: Node | undefined = undefined
        let newStartOffset: number | undefined = undefined
        let newEndContainer: Node | undefined = undefined
        let newEndOffset: number | undefined = undefined
        const selection = this.selection
        const toCheckNodeList = Utils.getTopNodeListInSelection(this.innerDocument.body, this.selectionRange)
        while (toCheckNodeList.length) {
            const firstNode = toCheckNodeList.shift()!
            if (selection.containsNode(firstNode, true)) {
                if (firstNode.nodeType === Node.TEXT_NODE) {
                    if (firstNode === startContainer) {
                        const sourceNodeValue = startContainer.nodeValue!
                        if (firstNode === endContainer) {
                            const newElText = sourceNodeValue.substring(startOffset, endOffset)
                            if (newElText) {
                                const newEl = document.createElement(format)
                                newEl.innerText = newElText
                                const remainTextLeft = sourceNodeValue.substring(0, startOffset)
                                if (remainTextLeft) {
                                    startContainer.nodeValue = remainTextLeft
                                    ;(startContainer as ChildNode).after(newEl)
                                } else {
                                    ;(startContainer as ChildNode).replaceWith(newEl)
                                }
                                const remainTextRight = sourceNodeValue.substring(endOffset)
                                if (remainTextRight) {
                                    const rightNode = document.createTextNode(remainTextRight)
                                    rightNode.nodeValue = remainTextRight
                                    newEl.after(rightNode)
                                }
                                newStartContainer = newEl.childNodes[0]
                                newStartOffset = 0
                                newEndContainer = newEl.childNodes[0]
                                newEndOffset = endOffset - startOffset
                            }
                        } else {
                            const newElText = sourceNodeValue.substring(startOffset)
                            if (newElText) {
                                const newEl = document.createElement(format)
                                newEl.innerText = newElText
                                const remainText = sourceNodeValue.substring(0, startOffset)
                                if (remainText) {
                                    firstNode.nodeValue = remainText
                                    firstNode.after(newEl)
                                } else {
                                    firstNode.replaceWith(newEl)
                                }
                                newStartContainer = newEl.childNodes[0]
                                newStartOffset = 0
                            }
                        }
                    } else if (firstNode === endContainer) {
                        const newElText = firstNode.nodeValue!.substring(0, endOffset)
                        if (newElText) {
                            const newEl = document.createElement(format)
                            newEl.innerText = newElText
                            const remainText = firstNode.nodeValue!.substring(endOffset)
                            if (remainText) {
                                firstNode.nodeValue = remainText
                                firstNode.before(newEl)
                            } else {
                                firstNode.replaceWith(newEl)
                            }
                            newEndContainer = newEl.childNodes[0]
                            newEndOffset = newEl.innerText.length
                        }
                    } else {
                        Utils.replaceTagName(firstNode, format)
                    }
                } else if (firstNode.nodeType === Node.ELEMENT_NODE) {
                    if (firstNode.nodeName !== format) {
                        toCheckNodeList.push(...firstNode.childNodes)
                    }
                }
            }
        }
        if (newStartContainer && newStartOffset !== undefined && newEndContainer && newEndOffset !== undefined) {
            this.selection.setBaseAndExtent(newStartContainer, newStartOffset, newEndContainer, newEndOffset)
        }
    }
}

export class Utils {
    static getTopParent(target: Node, rootNode: Node): Node | undefined {
        if (!target) {
            return undefined
        }
        if (!target.parentNode) {
            return undefined
        }
        if (target.parentNode === rootNode) {
            return target
        }
        return this.getTopParent(target.parentNode, rootNode)
    }
    static replaceTagName(el: Node, newTagName: string) {
        if (el.nodeType === Node.TEXT_NODE) {
            const newEl = document.createElement(newTagName)
            newEl.innerText = el.nodeValue!
            el.parentNode!.replaceChild(newEl, el)
            return newEl
        } else if (el.nodeType === Node.ELEMENT_NODE) {
            const newEl = document.createElement(newTagName)
            newEl.append(...el.childNodes)
            el.parentNode!.replaceChild(newEl, el)
            return newEl
        }
        return el
    }
    static mergeNodes(node: ChildNode, allowParentNextNode: boolean) {
        if (node.nodeType === Node.TEXT_NODE) {
            let nextTextNode: ChildNode | undefined = undefined
            const parentNode = node.parentNode!
            let nextNode = node.nextSibling
            if (!nextNode && allowParentNextNode) {
                nextNode = parentNode.nextSibling
            }
            if (nextNode) {
                nextTextNode = this.getMyFirstTextNodeOfTagName(nextNode, parentNode.nodeName)
                if (nextTextNode) {
                    const newNodeValue = (node.nodeValue ?? '') + (nextTextNode.nodeValue ?? '')
                    nextTextNode.nodeValue = newNodeValue
                    node.remove()
                }
                this.mergeNodes(nextNode, true)
            }
        } else if (node.nodeType === Node.ELEMENT_NODE) {
            const firstChild = node.firstChild
            if (firstChild) {
                this.mergeNodes(firstChild, true)
            }
        } else {
            const nextNode = node.nextSibling
            if (nextNode) {
                this.mergeNodes(nextNode, true)
            }
        }
    }
    static getMyFirstTextNodeOfTagName(containerNode: Node, tagName: string): ChildNode | undefined {
        if (containerNode.nodeType !== Node.ELEMENT_NODE) {
            return undefined
        }
        if (containerNode.nodeName !== tagName) {
            return undefined
        }
        const firstChild = containerNode.firstChild
        if (!firstChild) {
            return undefined
        }
        if (firstChild.nodeType === Node.ELEMENT_NODE) {
            return this.getMyFirstTextNodeOfTagName(firstChild, tagName)
        }
        if (firstChild.nodeType === Node.TEXT_NODE) {
            return firstChild
        }
        return undefined
    }
    static isEmpty(node: Node) {
        const arr = [node]
        while (arr.length) {
            const firstNode = arr.shift()!
            if (firstNode.nodeType === Node.TEXT_NODE) {
                if (firstNode.nodeValue) {
                    return false
                }
            } else if (firstNode.hasChildNodes()) {
                arr.push(...firstNode.childNodes)
            } else if (firstNode.nodeName === 'IMG') {
                return false
            }
        }
        return true
    }
    static createNewLine() {
        const p = document.createElement('p')
        const br = document.createElement('br')
        p.appendChild(br)
        return p
    }
    static getTopNodeListInSelection(rootNode: Node, selectionRange: Range) {
        const { startContainer, endContainer } = selectionRange
        const startNode = this.getTopParent(startContainer, rootNode)!
        const endNode = this.getTopParent(endContainer, rootNode)!
        const nodeList: ChildNode[] = []
        const childNodes: ChildNode[] = Array.from(rootNode.childNodes)
        const startIndex = childNodes.findIndex(a => a === startNode)
        const endIndex = childNodes.findIndex(a => a === endNode)
        for (let i = startIndex; i <= endIndex; i++) {
            nodeList.push(childNodes[i])
        }
        return nodeList
    }
}
